<html><head><meta charset="utf-8"><title>44 场景实战：ThreadLocal 在上下文传值场景下的实践 -慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        44 场景实战：ThreadLocal 在上下文传值场景下的实践 
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-11-28 09:55:13
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img4.mukewang.com/5ddde5980001246a06400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">生活的理想，就是为了理想的生活。<p class="author">——张闻天</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h2 id="开篇语" style="font-size: 30px;">开篇语</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们在 《打动面试官：线程池流程编排中的运用实战》一文中将流程引擎简单地完善了一下，本文在其基础上继续进行改造，建议同学可以先看看 GitHub 上的代码，或者看看之前的文章。</p>
</div><div class="cl-preview-section"><h2 id="回顾" style="font-size: 30px;">1 回顾</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">流程引擎编排的对象，我们称为组件（就是 SpringBean），之前我们给组件定义了通用的接口，组件实现时就实现这个接口，代码如下：<br>
<img class="" src="https://img.mukewang.com/5dd603980001289a16401246.png" data-original="//img.mukewang.com/5dd603980001289a16401246.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们定义了 DomainAbilityBean 接口，入参和出参都是 FlowContent，FlowContent 我们称为上下文。</p>
</div><div class="cl-preview-section"><h2 id="threadlocal-实现" style="font-size: 30px;">2 ThreadLocal 实现</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上下文传参除了 FlowContent 实现外，ThreadLocal 也是可以实现的，我们来演示一下：</p>
</div><div class="cl-preview-section"><h3 id="定义-threadlocal-上下文工具类">2.1 定义 ThreadLocal 上下文工具类</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们使用 ThreadLocal  定义了上下文工具类，并且定义了 put、get 方法，方便使用，代码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ContextCache</span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span>

  <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> serialVersionUID <span class="token operator">=</span> 2136539028591849277L<span class="token punctuation">;</span>

  <span class="token comment">// 使用 ThreadLocal 缓存上下文信息</span>
  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ThreadLocal<span class="token operator">&lt;</span>Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">&gt;&gt;</span> CACHE <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

  <span class="token comment">/**
   * 放数据
   * @param sourceKey
   */</span>
  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">putAttribute</span><span class="token punctuation">(</span>String sourceKey<span class="token punctuation">,</span>String value<span class="token punctuation">)</span><span class="token punctuation">{</span>
    Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">&gt;</span> cacheMap <span class="token operator">=</span> CACHE<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>null <span class="token operator">==</span> cacheMap<span class="token punctuation">)</span><span class="token punctuation">{</span>
      cacheMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    cacheMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>sourceKey<span class="token punctuation">,</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
    CACHE<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>cacheMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/**
   * 拿数据
   * @param sourceKey
   */</span>
  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String <span class="token function">getAttribute</span><span class="token punctuation">(</span>String sourceKey<span class="token punctuation">)</span><span class="token punctuation">{</span>
    Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">&gt;</span> cacheMap <span class="token operator">=</span> CACHE<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span><span class="token punctuation">(</span>null <span class="token operator">==</span> cacheMap<span class="token punctuation">)</span><span class="token punctuation">{</span>
      <span class="token keyword">return</span> null<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> cacheMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>sourceKey<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">如果你想往 ThreadLocal 放数据，调用 ContextCache.putAttribute 方法，如果想从 ThreadLocal 拿数据，调用 ContextCache.getAttribute 方法即可。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们写了两个组件，一个组件放数据，一个组件拿数据，如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd6037500016aa914521260.png" data-original="//img.mukewang.com/5dd6037500016aa914521260.png" alt="图片描述"><br>
我们把两个 SpringBean 注册到流程注册中心中，让其按照先执行 BeanThree 再执行 BeanFive 的顺序进行执行，运行 DemoApplication 类的 main 方法进行执行，执行结果如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd6033f0001448417820228.png" data-original="//img.mukewang.com/5dd6033f0001448417820228.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从打印的日志可以看到，在 Spring 容器管理的 SpringBean 中，ThreadLocal 也是可以储存中间缓存值的。</p>
</div><div class="cl-preview-section"><h2 id="开启子线程" style="font-size: 30px;">3 开启子线程</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们做一个实验，我们在 BeanFive 中开启子线程，然后再从 ThreadLocal 中拿值，看看能否拿到值，BeanFive 的代码修改成如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd6031e000120b717060666.png" data-original="//img.mukewang.com/5dd6031e000120b717060666.png" alt="图片描述"><br>
我们再来运行一下，打印的日志如下：<br>
<img class="" src="https://img.mukewang.com/5dd602f60001501620040500.png" data-original="//img.mukewang.com/5dd602f60001501620040500.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从打印的日志中，我们发现在子线程中从 ThreadLocal 取值时，并没有取得值，这个原因主要是我们之前说的，线程在创建的时候，并不会把父线程的 ThreadLocal 中的值拷贝给子线程的 ThreadLocal，解决方案就是把 ThreadLocal 修改成 InheritableThreadLocal，代码修改如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd602b800013f6f20360392.png" data-original="//img.mukewang.com/5dd602b800013f6f20360392.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们再次运行，结果如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd602950001394415020216.png" data-original="//img.mukewang.com/5dd602950001394415020216.png" alt="图片描述"><br>
从运行结果看，我们成功的在子线程中拿到值。</p>
</div><div class="cl-preview-section"><h3 id="线程池--threadlocal">4 线程池 + ThreadLocal</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">如果是拿数据的 springBean 是丢给线程池执行的，我们能够成功的从 ThreadLocal 中拿到数据么？</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们在放数据的 springBean 中，把放的值修改成随机的，接着拿数据的 SpringBean 修改成异步执行，代码修改如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd6026f0001812a19221304.png" data-original="//img.mukewang.com/5dd6026f0001812a19221304.png" alt="图片描述"><br>
为了能快速看到效果，我们把线程池的 coreSize 和 maxSize 全部修改成 3，并让任务沉睡一段时间，这样三个线程肯定消费不完任务，大量任务都会到队列中去排队，我们修改一下测试脚本，如下：<br>
<img class="" src="https://img.mukewang.com/5dd602380001a82413940340.png" data-original="//img.mukewang.com/5dd602380001a82413940340.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们期望的结果：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">线程池中执行的 BeanFive 可以成功从 ThreadLocal 中拿到数据；</li>
<li style="font-size: 20px; line-height: 38px;">能够从 ThreadLocal 拿到正确的数据，比如 BeanThree 刚放进 key1，value5，那么期望在 BeanFive 中根据 key1 能拿出 value5，而不是其它值。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们运行一下，结果如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd6021f00010d9a20260874.png" data-original="//img.mukewang.com/5dd6021f00010d9a20260874.png" alt="图片描述"><br>
从结果中可以看到，并没有符合我们的预期，我们往 ThreadLocal 中 put 进很多值，但最后拿出来的值却很多都是 value379，都为最后 put 到 ThreadLocal 中的值。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这个原因主要是 ThreadLocal 存储的 HashMap 的引用都是同一个，main 主线程可以修改 HashMap 中的值，子线程从 ThreadLocal 中拿值时，也是从 HashMap 中拿值，从而导致不能把 put 的值通过 ThreadLocal 正确的传递给子线程。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">为了证明是这个原因，我们在从  ThreadLocal 放、拿值的地方，把 HashMap 的内存地址都打印出来，改动代码如下：<br>
<img class="" src="https://img.mukewang.com/5dd601ed00012b0118101350.png" data-original="//img.mukewang.com/5dd601ed00012b0118101350.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们再次运行测试代码，运行的结果如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd601cf0001c0bd10150820.png" data-original="//img.mukewang.com/5dd601cf0001c0bd10150820.png" alt="图片描述"><br>
从测试结果中可以看到，不管是主线程还是子线程和 ThreadLocal 进行交互时，HashMap 都是同一个，也就是说 ThreadLocal 中保存的 HashMap 是共享的，这就导致了线程安全的问题，子线程读取到的值就会混乱掉。</p>
</div><div class="cl-preview-section"><h2 id="解决方案" style="font-size: 30px;">5 解决方案</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">针对这个问题，我们提出了一种解决方案，在把任务提交到线程池时，我们进行 HashMap 的拷贝，这样子线程的 HashMap 和 main 线程的 HashMap 就不同了，可以解决上面的问题。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们提交任务时， 使用的是 Runnable，要实现 HashMap 的拷贝的话，我们需要把 Runnable 进行一层包装，包装的代码如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5dd601610001af8b08020916.png" data-original="//img.mukewang.com/5dd601610001af8b08020916.png" alt="图片描述"><br>
运行结果如下:<br>
<img class="" src="https://img.mukewang.com/5dd6013f0001763b09370823.png" data-original="//img.mukewang.com/5dd6013f0001763b09370823.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从运行结果中可以看出，线程池拿出来的 value 已经是正确的了。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">6 总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本文通过 ThreadLocal 来改造流程引擎中的上下文传递，希望能够加深大家对 ThreadLocal 的认识和使用技巧，有兴趣的同学可以把我们的代码下载下来，跑跑看。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/885">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            43 ThreadLocal 源码解析   
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/887">
                                                                    <div class="next r clearfix">
                                        <p>
                                            45 Socket 源码及面试题  
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img3.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>